/*
    数据信息结构体：
        1、文件的实际存储路径：当客户端要下载文件时，从这个文件中读取数据进行响应
        2、文件压缩包的存储路径：当文件被压缩时，提供压缩包路径
        3、文件是否被压缩的标志位：判断这个文件是否已经被压缩
        4、文件大小
        5、文件最后修改时间
        6、文件最后访问时间
        7、文件访问URL中的资源路径path：/download/a.txt
        接口：填充结构体 -- 根据文件的路径自动获取信息填充已有的空结构体

    数据管理类：
        成员：
            1、用于数据信息访问：为了查询的速度快，使用hash表在内存中管理数据，以url的path作为key
            2、持久化存储管理：使用json序列化将所有文件属性保存在文件中
            3、读写锁：读共享，写互斥
        接口：
            1、构造函数
            2、每次修改数据后都要重新持久化存储，避免数据丢失
            3、每次系统重启后都要加载以前的数据
            4、新增数据
            5、修改数据
            6、根据URL查询
            7、根据实际存储路径查询
            8、查询所有信息
*/

#ifndef __M_DATA_H__
#define __M_DATA_H__

#include <unordered_map>
#include <pthread.h>
#include "util.hpp"
#include "config.hpp"

namespace cloud{
    struct BackupInfo{
        bool _iscompressed;     //标志位，标志该文件是否被压缩
        size_t _fsize;  //文件大小
        time_t _lmtime; //最后修改时间
        time_t _latime; //最后访问时间
        string _fpath;  //未被压缩的文件路径
        string _ppath;  //被压缩文件的压缩包路径
        string _url;

        //填充新结构体
        bool fillNewInfo(const string &path){

            FileUtil fu(path);
            if(fu.isExist() == false){
                cerr << "fillNewInfo():path is not exist!" << endl;
                return false;
            }
            _iscompressed = false;
            _fsize = fu.fileSize();
            _lmtime = fu.lastModifyTime();
            _latime = fu.lastAccessTime();
            _fpath = path;

            //构造压缩包路径
            //用单例类获取压缩包所在目录以及压缩包后缀
            Config *config = Config::getInstance();
            string packdir = config->getPackDir();
            string packsuffix = config->getPackfileSuffix();
            //压缩包所在目录目录 + 文件名 + 后缀 = 压缩包路径
            _ppath = packdir + fu.fileName() + packsuffix;

            //构造下载url
            string download_prefix = config->getDownloadPrefix();
            _url = download_prefix + fu.fileName();

            return true;
        }
    };

    class DataController{
    public:
        DataController(){
            // 通过单例对象获取备份信息文件路径
            _backup_file = Config::getInstance()->getBackupFile();
            //初始化读写锁
            pthread_rwlock_init(&_rwlock, nullptr);
            //初始化已有的文件属性
            initLoad();
        }

        ~DataController(){
            pthread_rwlock_destroy(&_rwlock);
        }

        bool insert(const BackupInfo &info){
            //由于storage()要调用getAll()，而getAll()要申请写锁，如果用RAII写会导致getAll()阻塞，所以还是需要手动写
            // RWLock _lock(&_rwlock, WorR::WRITE);
            pthread_rwlock_wrlock(&_rwlock);
            //<key, value> == <info._utl, info>
            _table[info._url] = info;
            pthread_rwlock_unlock(&_rwlock);
            storage();
            return true;
        }

        bool update(const BackupInfo &info){
            // RWLock _lock(&_rwlock, WorR::WRITE);
            pthread_rwlock_wrlock(&_rwlock);
            _table[info._url] = info;
            pthread_rwlock_unlock(&_rwlock);
            storage();
            return true;
        }


        //用于删除文件以及压缩包，而不是在压缩后删除原文件
        bool remove(BackupInfo &info){
            // RWLock _lock(&_rwlock, WorR::WRITE);
            pthread_rwlock_wrlock(&_rwlock);
            std::remove(info._fpath.c_str());
            _table.erase(info._url);
            pthread_rwlock_unlock(&_rwlock);
            storage();
            return true;
        }

        bool getByURL(const string& url, BackupInfo *info){
            // RWLock _lock(&_rwlock, WorR::WRITE);
            pthread_rwlock_wrlock(&_rwlock);
            //因为url是key，所以可以直接通过url进行查找
            auto it = _table.find(url);
            if(it == _table.end()){
                pthread_rwlock_unlock(&_rwlock);
                return false;
            }
            *info = it->second;
            pthread_rwlock_unlock(&_rwlock);
            return true;
        }

        bool getByPath(const string &path, BackupInfo *info){
            // RWLock _lock(&_rwlock, WorR::WRITE);
            pthread_rwlock_wrlock(&_rwlock);
            //这里path不是key，要先遍历table，对比table的value中的_fpath和path
            for(auto it : _table){
                if(it.second._fpath == path){
                    *info = it.second;
                    pthread_rwlock_unlock(&_rwlock);
                    return true;
                }
            }
            pthread_rwlock_unlock(&_rwlock);
            return false;
        }

        bool getAll(vector<BackupInfo> *arr){
            // RWLock _lock(&_rwlock, WorR::WRITE);
            pthread_rwlock_wrlock(&_rwlock);
            for(auto it : _table){
                arr->push_back(it.second);
            }
            pthread_rwlock_unlock(&_rwlock);
            return true;
        }


        //保存文件属性信息：获取所有文件的属性，序列化后存储到文件中
        bool storage(){
            
            // 1、获取所有文件
            vector<BackupInfo> arr;
            getAll(&arr);

            //2、添加到Json:Value
            Json::Value root;
            for(auto v : arr){
                Json::Value item;
                item["iscompressed"] = v._iscompressed;
                item["fsize"] = (Json::Int64)v._fsize;
                item["lmtime"] = (Json::Int64)v._lmtime;
                item["latime"] = (Json::Int64)v._latime;
                item["fpath"] = v._fpath;
                item["ppath"] = v._ppath;
                item["url"] = v._url;
                root.append(item);
            }
            //3、序列化
            string body;
            JsonUtil::serialize(root, &body);
            //4、写入文件
            FileUtil fu(_backup_file);
            fu.setContent(body);

            return true;
        }

        bool initLoad(){
            //1、将文件属性文件中的数据读取出来
            FileUtil fu(_backup_file);
            if(fu.isExist() == false){
                return true;
            }
            string body;
            fu.getContent(&body);

            //2、反序列化
            Json::Value root;
            JsonUtil::unserialize(body, &root);

            //3、将反序列化得到的数据添加到_table
            for (int i = 0; i < root.size();i++){
                BackupInfo info;
                info._iscompressed = root[i]["iscompressed"].asBool();
                info._fsize = root[i]["fsize"].asInt();
                info._latime = root[i]["atime"].asInt();
                info._lmtime = root[i]["lmtime"].asInt();
                info._fpath = root[i]["fpath"].asString();
                info._ppath = root[i]["ppath"].asString();
                info._url = root[i]["url"].asString();
                insert(info);
            }
                return true;
        }

    private:
        string _backup_file;        //备份文件：保存所有文件属性
        pthread_rwlock_t _rwlock;   //读写锁
        unordered_map<string, BackupInfo> _table;   //哈希表
    };
}

#endif